package com.cloudkinto.utils.amazon;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cloudkinto.common.constant.GlobalConstants;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.SHA256Utils;
import com.cloudkinto.common.utils.TimeUtils;
import com.cloudkinto.common.utils.XmlUtils;
import com.cloudkinto.entity.ShopDo;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import static com.cloudkinto.common.utils.OkHttpUtils.getUnsafeOkHttpClient;

/**
 * @auther: hujun
 * @date: 2019/5/8 14:19
 * @email: 461214737@qq.com
 * @description: 亚马逊订单api请求工具类
 */
@Slf4j
public class AmazonUtil {
    /***post请求地址*/
    private static final String POST_URL = GlobalConstants.AmazonURL;

    /***post请求后缀*/
    private static final String SUFFIX_POST_URL = "/Orders/2013-09-01";
    /***订单api版本*/
    private static final String VERSION = "2009-01-01";
    /****请求的Amazon方法*/
    private static final String ACTION = "SubmitFeed";
    /***签名方式*/
    private static final String ALGORITHM = "HmacSHA256";

    /****时间转换格式*/
    private static final String TIME_FORMAT_STR = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";

    /****编码格式*/
    private static final String CHARACTER_ENCODING = "UTF-8";

    private static final String SIGNATURE_VERSION = "2";
    private static final String FEEDTYPE = "_POST_ORDER_FULFILLMENT_DATA_";

    /**
     * 固定数据
     */
    private static String regionName = "us-west-2";
    private static String serviceName = "execute-api";
    /***订单中的所有商品均已发货*/
    String SHIPPED = "Shipped";
    private static final Logger logger = LoggerFactory.getLogger(AmazonUtil.class);

    /***
     * post请求通道
     * @param secretKey
     * @param urlParameters
     * @return
     * @throws Exception
     */
    public static String doPostOrderList(String nowDate, String beforeDate, String afterDate, String url, Map<String, String> urlParameters, String secretKey, String requestBody) throws Exception {
        urlParameters.put("Timestamp", urlEncode(nowDate));
        urlParameters.put("Action", urlEncode("ListOrders"));
        urlParameters.put("SignatureMethod", urlEncode(ALGORITHM));
        urlParameters.put("Version", urlEncode("2013-09-01"));
        urlParameters.put("SignatureVersion", urlEncode(SIGNATURE_VERSION));
        if (StringUtils.isNotBlank(beforeDate))
            urlParameters.put("LastUpdatedBefore", urlEncode(beforeDate));
        if (StringUtils.isNotBlank(afterDate))
            urlParameters.put("LastUpdatedAfter", urlEncode(afterDate));
        String formattedParameters = calculateStringToSignV2(urlParameters, url);
        logger.info("签名内容：{}", formattedParameters);
        String signature = sign(formattedParameters, secretKey);
        logger.info("签名signature: {}", signature);
        urlParameters.put("Signature", urlEncode(signature));
        String paramStr = sortParams(new StringBuilder(), urlParameters);
        logger.info("排序后参数：{}", paramStr);
        logger.info("url  " + url + "?" + paramStr);
        return doPost(url + "?" + paramStr, requestBody);
    }

    /***
     * spOrderList
     * @param url
     * @param accessToken
     * @param sessionToken
     * @param parametersMap
     * @return
     * @throws Exception
     */
    public static String spOrderList(String url, String accessToken, String sessionToken, Map<String, Object> parametersMap) throws Exception {
        String body = spAPidoGet(url, accessToken, sessionToken, parametersMap);
        return body;
    }

    /***
     * spOrderList
     * @param url
     * @param parametersMap
     * @return
     * @throws Exception
     */
    public static String awsSTS(String url, Map<String, Object> parametersMap) throws Exception {
        String body = spAPidoSTS(url, regionName, "sts", parametersMap, "");
        return body;
    }

    /***
     * spOrderDetail
     * @param url
     * @param parametersMap
     * @param accssToken
     * @return
     * @throws Exception
     */
    public static String spOrder(String url, String accssToken, String sessionToken, Map<String, String> parametersMap) throws Exception {
        String body = spAPidoGet(url, accssToken, sessionToken,
                parametersMap, ""
        );
        return body;
    }


    /***
     * Feeds 创建Feeds模板
     * createFeedsDocuments
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static String createFeedsDocuments(String accessToken, String sessionToken) throws Exception {
        Map<String, String> parametersMap = new HashMap<>();
        parametersMap.put("contentType", "text/xml");
        System.out.println(SHA256Utils.getSHA256(JSON.toJSONString(parametersMap)));
        String body = AmazonUtil.spAPidoPOST("https://sellingpartnerapi-fe.amazon.com/feeds/2021-06-30/documents",
                accessToken, sessionToken, parametersMap, JSON.toJSONString(parametersMap));
        return body;
    }

    /***
     * Reports 获取报告get
     * @param accessToken
     * @param reportId
     * @return
     * @throws Exception
     */
    public static String getReports(String accessToken, String sessionToken, long reportId) throws Exception {
        Map<String, Object> parametersMap = new HashMap<>();
//        List<String> marketplaceIds = new ArrayList<>();
//        List<String> reportTypes = new ArrayList<>();
//        reportTypes.add("GET_FLAT_FILE_OPEN_LISTINGS_DATA");
//        marketplaceIds.add(GlobalConstants.marketplaceId);
//        parametersMap.put("reportTypes", "GET_FLAT_FILE_OPEN_LISTINGS_DATA");
//        parametersMap.put("marketplaceIds", "" + GlobalConstants.marketplaceId + "");
        System.out.println(SHA256Utils.getSHA256(JSON.toJSONString(parametersMap)));
        String url = "https://sellingpartnerapi-fe.amazon.com/reports/2021-06-30/reports";
        if (reportId != 0) url = url + "/" + reportId;
        String body = AmazonUtil.spAPidoGet(url,
                accessToken, sessionToken, parametersMap, "");
        return body;
    }

    /***
     * Reports 创建报告post
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static String createReports(String accessToken, String sessionToken, String reportType, String dataStartTime, String dataEndTime) throws Exception {
        Map<String, Object> parametersMap = new HashMap<>();
        List<String> marketplaceIds = new ArrayList<>();
        marketplaceIds.add(GlobalConstants.marketplaceId);
        parametersMap.put("reportType", reportType);
        parametersMap.put("marketplaceIds", marketplaceIds);
        if (StringUtils.isNotBlank(dataStartTime))
            parametersMap.put("dataStartTime", dataStartTime);
        if (StringUtils.isNotBlank(dataEndTime))
            parametersMap.put("dataEndTime", dataEndTime);
        System.out.println(SHA256Utils.getSHA256(JSON.toJSONString(parametersMap)));
        String body = AmazonUtil.spAPidoPOST("https://sellingpartnerapi-fe.amazon.com/reports/2021-06-30/reports",
                accessToken, sessionToken, parametersMap, JSONObject.toJSONString(parametersMap));
        return body;
    }

    /***
     * Feeds 加密文件并上传 （上传追踪单号）
     * createFeedsDocuments
     * @param object 真实数据
     * @param sellerId 卖家id
     * @param url 上传地址url
     * @return
     * @throws Exception
     */
    public static String uploadFeedsDocumentsFile(Object object, String sellerId, String url) throws Exception {
        String requestBody = XmlUtils.convertToXml(object, "<AmazonEnvelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
                "xsi:noNamespaceSchemaLocation=\"amzn-envelope.xsd\">\n<Header>\n" +
                " <DocumentVersion>1.02</DocumentVersion>\n" +
                " <MerchantIdentifier>" + sellerId + "</MerchantIdentifier>\n" +
                " </Header>\n<MessageType>OrderFulfillment</MessageType>", "</AmazonEnvelope>");
        System.out.println(requestBody);
        logger.info(requestBody);
        String contentType = "text/xml; charset=UTF-8";
        String body = upload(url, contentType, requestBody.getBytes());
        System.out.println(body);
        logger.info(body);
        return body;
    }

    /***
     * spGetToken
     * @param clientId
     * @param clientSecret
     * @param lwa
     * @return
     * @throws Exception
     */
    public static String spGetToken(String clientId, String clientSecret, String lwa, String sessionToken) throws Exception {
        Map parametersMap = new HashMap<>();
        if (StringUtils.isNotBlank(lwa)) {
            parametersMap.put("grant_type", "refresh_token");
            parametersMap.put("refresh_token", lwa);
        } else {
            parametersMap.put("grant_type", "client_credentials");
            parametersMap.put("scope", "sellingpartnerapi::migration");
        }
        parametersMap.put("client_id", clientId);
        parametersMap.put("client_secret", clientSecret);
        String body = spAPidoPOST("https://api.amazon.com/auth/o2/token", "", sessionToken, parametersMap, "");
        String access_token = JSONObject.parseObject(body).getString("access_token");
        String refresh_token = JSONObject.parseObject(body).getString("refresh_token");
        //todo 把数据存储在数据库
        return access_token;
    }


    /***
     * spGetRdtToken(PII数据Token)
     * @param url
     * @param path
     * @param dataElements
     * @return
     * @throws Exception
     */
    public static String spGetRdtToken(String url, String path, List<String> dataElements, String accessToken, String sessionTokenData) throws
            Exception {
        Map parametersMap = new HashMap<>();
        List<Map> dataList = new ArrayList<>();
        Map dataMap = new HashMap<>();

        dataMap.put("method", "GET");
        dataMap.put("path", path);
        dataMap.put("dataElements", dataElements);
        dataList.add(dataMap);
        parametersMap.put("restrictedResources", dataList);
        String body = spAPidoPOST(url + "/tokens/2021-03-01/restrictedDataToken", accessToken, sessionTokenData, parametersMap, JSON.toJSONString(parametersMap));
        String refresh_token = JSONObject.parseObject(body).getString("restrictedDataToken");
        //todo 把数据存储在数据库
        return refresh_token;
    }

    /***
     * spGetToken
     * @param clientId
     * @param clientSecret
     * @param code
     * @return
     * @throws Exception
     */
    public static String spGetAdToken(String clientId, String clientSecret, String refreshToken, String code) throws Exception {
        Map parametersMap = new HashMap<>();
        if (StringUtils.isNotBlank(refreshToken)) {
            parametersMap.put("grant_type", "refresh_token");
            parametersMap.put("refresh_token", refreshToken);
        } else {
            parametersMap.put("grant_type", "authorization_code");
            parametersMap.put("code", code);
        }
        parametersMap.put("client_id", clientId);
        parametersMap.put("client_secret", clientSecret);
        parametersMap.put("redirect_uri", "https://www.cloudkinto.com:8082/api/admin/amazon");
        String body = spAPidoPOST("https://api.amazon.co.jp/auth/o2/token", "", "", parametersMap, "");
        String access_token = JSONObject.parseObject(body).getString("access_token");
        String refresh_token = JSONObject.parseObject(body).getString("refresh_token");
        //todo 把数据存储在数据库
        return access_token;
    }

    /***
     * spGetToken
     * @param clientId
     * @param clientSecret
     * @param code
     * @return
     * @throws Exception
     */
    public static String spGetTokenByCode(String clientId, String clientSecret, String code, String sessionToken) throws Exception {
        Map parametersMap = new HashMap<>();
        if (StringUtils.isNotBlank(code)) {
            parametersMap.put("grant_type", "authorization_code");
            parametersMap.put("code", code);
//            parametersMap.put("redirect_uri", "www.cloudkinto.com");
        } else {
            parametersMap.put("grant_type", "client_credentials");
            parametersMap.put("scope", "sellingpartnerapi::migration");
        }
        parametersMap.put("client_id", clientId);
        parametersMap.put("client_secret", clientSecret);
        String body = spAPidoPOST("https://api.amazon.com/auth/o2/token", "", sessionToken, parametersMap, "");
        String access_token = JSONObject.parseObject(body).getString("access_token");
        String refresh_token = JSONObject.parseObject(body).getString("refresh_token");
        //todo 把数据存储在数据库
        return access_token;
    }

    /***
     * spGetToken
     * @param sellingPartnerId
     * @param developerId
     * @param mwsAuthToken
     * @return
     * @throws Exception
     */
    public static String spGetAuthCodeByDevId(String sellingPartnerId, String developerId,String mwsAuthToken,String sessionToken) throws Exception {
        Map parametersMap = new HashMap<>();
        parametersMap.put("sellingPartnerId", sellingPartnerId);
        parametersMap.put("developerId", developerId);
        parametersMap.put("mwsAuthToken", mwsAuthToken);
        String body = spAPidoGet("https://sellingpartnerapi-fe.amazon.com/authorization/v1/authorizationCode", "", "", parametersMap);
        String access_token = JSONObject.parseObject(body).getString("code");
        //todo 把数据存储在数据库
        return access_token;
    }

    private String getAccessToken(ShopDo shopDo) throws Exception {
        String sessionTokenData = getAwsSTSSessionToken();
        return AmazonUtil.spGetToken(GlobalConstants.AmzSpClientId, GlobalConstants.AmzSpClientSecret, shopDo.getAmzSpLwa(), sessionTokenData);
    }

    private String getAwsSTSSessionToken() {
        Map parametersMap = new HashMap<>();
        parametersMap.put("Action", "AssumeRole");
        parametersMap.put("Version", "2011-06-15");
        parametersMap.put("RoleArn", "arn:aws:iam::975458911230:role/FBA-SP-API");
        parametersMap.put("RoleSessionName", "dingSession");
        String body = null;
        try {
            body = AmazonUtil.awsSTS("https://sts.us-west-2.amazonaws.com/", parametersMap);
            JSONObject jsonObject = XmlUtils.xml2Json(body);
            JSONObject resultData = jsonObject.getJSONObject("AssumeRoleResponse").getJSONArray("AssumeRoleResult").getJSONObject(0).getJSONArray("Credentials").getJSONObject(0);
            GlobalConstants.accessSecrityKeySP = resultData.getString("AccessKeyId");
            GlobalConstants.secretSecrityKeySP = resultData.getString("SecretAccessKey");
            String sessionToken = resultData.getString("SessionToken");
            return sessionToken;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public static String doPostOrderListByToken(String nowDate, String nextToken, String url, Map params, String amazonSecret, String requestBody) throws Exception {
        if (params.containsKey("Signature")) {
            params.remove("Signature");
        }
        if (params.containsKey("AmazonOrderId")) {
            params.remove("AmazonOrderId");
        }
        params.put("Timestamp", urlEncode(nowDate));
        params.put("Action", urlEncode("ListOrdersByNextToken"));
        params.put("SignatureMethod", urlEncode(ALGORITHM));
        params.put("Version", urlEncode("2013-09-01"));
        params.put("NextToken", urlEncode(nextToken));
        params.put("SignatureVersion", urlEncode(SIGNATURE_VERSION));
        String formattedParameters = calculateStringToSignV2(params, url);
        logger.info("签名内容：{}", formattedParameters);
        String signature = sign(formattedParameters, amazonSecret);
        logger.info("签名signature: {}", signature);
        params.put("Signature", urlEncode(signature));
        String paramStr = sortParams(new StringBuilder(), params);
        logger.info("排序后参数：{}", paramStr);
        logger.info("url  " + url + "?" + paramStr);
        return doPost(url + "?" + paramStr, requestBody);

    }

    /***
     * post请求通道
     * @param secretKey
     * @param urlParameters
     * @return
     * @throws Exception
     */
    public static String doPostSubmitFeed(String url, HashMap<String, String> urlParameters, String secretKey, String requestBudy) throws Exception {
        String now = new SimpleDateFormat(GlobalConstants.TIME_FORMAT_STR).format(new Date(System.currentTimeMillis() - 8 * 60 * 60 * 1000));
        /**++++++++++++++++++++++++++++++获取当天前00:00:00分-23:59:59的信息 查找过去一天的时间段的数据信息++++++++++++++++++++++++++++++*/
        // long zero = LocalDateTime.of(LocalDate.now(), LocalTime.of(8,0)).toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        //String after =new DateTime(zero-1000*3600*24, DateTimeZone.UTC).toString(TIME_FORMAT_STR);
        //String before =new DateTime(zero, DateTimeZone.UTC).toString(TIME_FORMAT_STR);
        /**++++++++++++++++++++++++++++++++++++以上获取过去整点一天的订单数据信息++++++++++++++++++++++++++++++++++++++++++*/

        urlParameters.put("Timestamp", urlEncode(now));
        urlParameters.put("Action", urlEncode(ACTION));
        urlParameters.put("FeedType", urlEncode(FEEDTYPE));
        urlParameters.put("SignatureMethod", urlEncode(ALGORITHM));
        urlParameters.put("Version", urlEncode(VERSION));
        urlParameters.put("SignatureVersion", urlEncode(SIGNATURE_VERSION));
        String formattedParameters = calculateSubmitFeedStringToSignV2(urlParameters, POST_URL);
        logger.info("签名内容：{}", formattedParameters);
        String signature = sign(formattedParameters, secretKey);
        logger.info("签名signature: {}", signature);
        urlParameters.put("Signature", urlEncode(signature));
        urlParameters.put("Timestamp", now);
        String paramStr = sortParams(new StringBuilder(), urlParameters);
        logger.info("排序后参数：{}", paramStr);
        return doPost(url + "?" + paramStr, requestBudy);

    }


    public static String doPostSubmitFeedItem(String nowDate, String url, Map<String, String> urlParameters, String secretKey) throws Exception {
        urlParameters.put("Timestamp", urlEncode(nowDate));
        urlParameters.put("Action", urlEncode("ListOrderItems"));
        urlParameters.put("SignatureMethod", urlEncode(ALGORITHM));
        urlParameters.put("Version", urlEncode("2013-09-01"));
        urlParameters.put("SignatureVersion", urlEncode(SIGNATURE_VERSION));


        String formattedParameters = calculateStringToSignV2(urlParameters, url);
        logger.info("签名内容：{}", formattedParameters);
        String signature = sign(formattedParameters, secretKey);
        logger.info("签名signature: {}", signature);
        urlParameters.put("Signature", urlEncode(signature));
        String paramStr = sortParams(new StringBuilder(), urlParameters);
        System.out.println("params : ================" + urlParameters.toString());
        logger.info("url  " + url + "?" + paramStr);
        return doPost(url + "?" + paramStr, "");
    }


    /**
     * signV2签名内容
     *
     * @param parameters
     * @param serviceUrl
     * @return
     * @throws SignatureException
     * @throws URISyntaxException
     */
    private static String calculateStringToSignV2(
            Map<String, String> parameters, String serviceUrl)
            throws URISyntaxException {
        URI endpoint = new URI(serviceUrl);
        StringBuilder data = new StringBuilder();
        data.append("POST\n");
        data.append(endpoint.getHost());
        data.append("\n");
        data.append(endpoint.getPath());
        data.append("\n");
        return sortParams(data, parameters);
    }

    /**
     * signV2签名内容
     *
     * @param parameters
     * @param serviceUrl
     * @return
     * @throws SignatureException
     * @throws URISyntaxException
     */
    private static String calculateSubmitFeedStringToSignV2(
            Map<String, String> parameters, String serviceUrl)
            throws URISyntaxException {
        URI endpoint = new URI(serviceUrl);
        StringBuilder data = new StringBuilder();
        data.append("POST\n");
        data.append(endpoint.getHost());
        data.append("\n/");
        data.append(endpoint.getPath());
        data.append("\n");
        return sortParams(data, parameters);
    }

    /***
     * signV2签名方式
     * @param data
     * @param secretKey
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws IllegalStateException
     * @throws UnsupportedEncodingException
     */
    private static String sign(String data, String secretKey)
            throws NoSuchAlgorithmException, InvalidKeyException,
            IllegalStateException, UnsupportedEncodingException {
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(new SecretKeySpec(secretKey.getBytes(CHARACTER_ENCODING),
                ALGORITHM));
        byte[] signature = mac.doFinal(data.getBytes(CHARACTER_ENCODING));
        String signatureBase64 = new String(Base64.encodeBase64(signature),
                CHARACTER_ENCODING);
        return signatureBase64;
    }

    /**
     * url非法字符转换
     *
     * @param rawValue
     * @return
     */
    public static String urlEncode(String rawValue) {
        String value = (rawValue == null) ? "" : rawValue;
        String encoded = null;

        try {
            encoded = URLEncoder.encode(value, CHARACTER_ENCODING)
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            System.err.println("Unknown encoding: " + CHARACTER_ENCODING);
        }

        return encoded;
    }

    /**
     * SP-API
     *
     * @param serviceUrl
     * @param parametersMap
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static String spAPidoGet(String serviceUrl, String accessToken, String sessionToken, Map<String, Object> parametersMap) throws Exception {
        return spAPidoGet(serviceUrl, accessToken, sessionToken, regionName, serviceName, parametersMap, "");
    }

    /**
     * SP-API 通用GET方法
     *
     * @param serviceUrl
     * @param parametersMap
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static String spAPidoGet(String serviceUrl, String accessToken, String sessionToken, Map parametersMap, String payLoad) throws Exception {
        return spAPidoGet(serviceUrl, accessToken, sessionToken, regionName, serviceName, parametersMap, payLoad);
    }

    /**
     * SP-API 通用GET方法
     *
     * @param serviceUrl
     * @param accessToken
     * @param regionName    请求的范围
     * @param parametersMap
     * @param payLoad
     * @return
     * @throws Exception
     */
    public static String spAPidoGet(String serviceUrl, String accessToken, String sessionToken, String regionName, String serviceName, Map parametersMap, String payLoad) throws Exception {
        String timeStamp = getTimeStamp();
        String dateStamp = getDateStamp();
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("host", new URI(serviceUrl).getHost());
        headerMap.put("user-agent", GlobalConstants.amazonAppId + "/1.0 (Language=Java/1.8.0.221;Platform=Windows/10)");
//        headerMap.put("content-type", "application/json; charset=utf-8");
        if (StringUtils.isNotBlank(accessToken))
            headerMap.put("x-amz-access-token", accessToken);
        headerMap.put("x-amz-date", timeStamp);
        if (StringUtils.isNotBlank(sessionToken))
            headerMap.put("x-amz-security-token", sessionToken);
        if (parametersMap.size() > 0)
            serviceUrl = serviceUrl + "?" + sortParams(parametersMap);
        String sign = byteToHex(getSignatureData(getSignatureKey(GlobalConstants.secretSecrityKeySP, dateStamp,
                regionName, serviceName), getSignatureContent(timeStamp, getCredential(dateStamp, regionName, serviceName),
                SHA256Utils.getSHA256(canonicalRequestString("GET", parametersMap, headerMap, serviceUrl, payLoad)))));
        System.out.println("结果：\n" + sign);
        OkHttpClient client = getUnsafeOkHttpClient();
        Request.Builder requestBuilder = createSPHeader(headerMap);
        requestBuilder.addHeader("Authorization", "AWS4-HMAC-SHA256 Credential=" + GlobalConstants.accessSecrityKeySP + "/" +
                getCredential(dateStamp, regionName, serviceName)
                + ",SignedHeaders=" + sortSignHeaderParams(headerMap) + ",Signature=" + sign);
        Request request = requestBuilder.url(serviceUrl).build();
        Response response = null;
        String body = "";
        try {
            response = client.newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body;
    }

    /**
     * SP-API SP-API 通用POST方法
     *
     * @param serviceUrl    接口地址
     * @param accessToken   访问令牌
     * @param sessionToken  secret令牌，请求PII数据
     * @param parametersMap body参数
     * @param payLoad       参数toString
     * @return
     * @throws Exception
     */
    public static String spAPidoPOST(String serviceUrl, String accessToken, String sessionToken, Map parametersMap, String payLoad) throws Exception {
        return spAPidoPOST(serviceUrl, accessToken, sessionToken, GlobalConstants.secretSecrityKeySP, GlobalConstants.accessSecrityKeySP, parametersMap, payLoad);
    }

    /**
     * @param serviceUrl    接口地址
     * @param accessToken   访问令牌
     * @param sessionToken  secret令牌，请求PII数据
     * @param secretKeySP   secret令牌的持有人私钥
     * @param accessKeySP   secret令牌的持有人公钥
     * @param parametersMap body参数
     * @param payLoad       参数toString
     * @return
     * @throws Exception
     */
    public static String spAPidoPOST(String serviceUrl, String accessToken, String sessionToken, String secretKeySP, String accessKeySP, Map parametersMap, String payLoad) throws Exception {
        String timeStamp = getTimeStamp();
        String dateStamp = getDateStamp();
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("host", new URI(serviceUrl).getHost());
        headerMap.put("user-agent", GlobalConstants.amazonAppId + "/1.0 (Language=Java/1.8.0.221;Platform=Windows/10)");
        headerMap.put("content-type", "application/json; charset=utf-8");
        if (StringUtils.isNotBlank(accessToken))
            headerMap.put("x-amz-access-token", accessToken);
        if (StringUtils.isNotBlank(sessionToken))
            headerMap.put("x-amz-security-token", sessionToken);
        headerMap.put("x-amz-date", timeStamp);
        String sign = byteToHex(getSignatureData(getSignatureKey(secretKeySP, dateStamp,
                regionName, serviceName), getSignatureContent(timeStamp, getCredential(dateStamp, regionName, serviceName),
                SHA256Utils.getSHA256(canonicalRequestString("POST", new HashMap<>(), headerMap, serviceUrl, payLoad)))));
        System.out.println("结果：\n" + sign);
        OkHttpClient client = getUnsafeOkHttpClient();
        Request.Builder requestBuilder = createSPHeader(headerMap);
        if (StringUtils.isNotBlank(accessToken))
            requestBuilder.addHeader("Authorization", "AWS4-HMAC-SHA256 Credential=" + accessKeySP + "/" +
                    getCredential(dateStamp, regionName, serviceName)
                    + ",SignedHeaders=" + sortSignHeaderParams(headerMap) + ",Signature=" + sign);
        Request request = requestBuilder.url(serviceUrl)
                .post(RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(parametersMap)))
                .build();
        System.out.println("requestBody:" + JSON.toJSONString(parametersMap));
        Response response = null;
        String body = "";
        try {
            response = client.newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body;

    }

    private static String spAPidoSTS(String serviceUrl, String regionName, String serviceName, Map<String, Object> parametersMap, String payLoad) throws Exception {
        String timeStamp = getTimeStamp();
        String dateStamp = getDateStamp();
        Map<String, String> headerMap = new HashMap<>();
        headerMap.put("host", new URI(serviceUrl).getHost());
        headerMap.put("content-type", "application/json; charset=utf-8");
        headerMap.put("x-amz-date", timeStamp);
        if (parametersMap.size() > 0)
            serviceUrl = serviceUrl + "?" + sortParams(parametersMap);
        String sign = byteToHex(getSignatureData(getSignatureKey(GlobalConstants.secretKeySP, dateStamp,
                regionName, serviceName), getSignatureContent(timeStamp, getCredential(dateStamp, regionName, serviceName),
                SHA256Utils.getSHA256(canonicalRequestString("GET", parametersMap, headerMap, serviceUrl, payLoad)))));
        System.out.println("结果：\n" + sign);
        OkHttpClient client = getUnsafeOkHttpClient();
        Request.Builder requestBuilder = createSPHeader(headerMap);
        requestBuilder.addHeader("Authorization", "AWS4-HMAC-SHA256 Credential=" + GlobalConstants.accessKeySP + "/" +
                getCredential(dateStamp, regionName, serviceName)
                + ",SignedHeaders=" + sortSignHeaderParams(headerMap) + ",Signature=" + sign);
        Request request = requestBuilder.url(serviceUrl).build();
        Response response = null;
        String body = "";
        try {
            response = client.newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body;
    }

    /**
     * s3 上传文件
     *
     * @param url                 地址
     * @param contentType         编码格式
     * @param source              原始数据流
     * @return
     * @throws Exception
     */
    public static String upload(String url, String contentType, byte[] source) throws Exception {

        URI uri = new URI(url);
        Map<String, String> headerMap = new HashMap<>();
        Map<String, String> paramsMap = MapUrlParamsUtils.getUrlParams(uri.getQuery());
        //时间戳去url的query参数中的值
//        String timeStamp = paramsMap.get("X-Amz-Date");
//        String dateStamp = paramsMap.get("X-Amz-Date").split("T")[0];
        //加密参数去除Signature
        paramsMap.remove("X-Amz-Signature");
//        String serviceName = "s3";
        headerMap.put("host", uri.getHost());
        headerMap.put("content-type", contentType);
//        String sign = byteToHex(getSignatureData(getSignatureKey(GlobalConstants.secretKeySP, dateStamp,
//                regionName, serviceName), getSignatureContent(timeStamp, getCredential(dateStamp, regionName, serviceName),
//                SHA256Utils.getSHA256(canonicalRequestString("PUT", paramsMap, headerMap, url, source)))));
//        System.out.println("结果：\n" + sign);
        Request.Builder requestBuilder = createSPHeader(headerMap);
        //把流文件加密再返回byte格式
        InputStream inputStream = new ByteArrayInputStream(source);
        byte[] bytes = null;
        try {
            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            byte[] buff = new byte[100];
            int rc = 0;
            while ((rc = inputStream.read(buff, 0, 100)) > 0) {
                swapStream.write(buff, 0, rc);
            }
            bytes = swapStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Request request = requestBuilder
                .url(url)
                .put(RequestBody.create(MediaType.parse(contentType), bytes))
                .build();
        Response response = getUnsafeOkHttpClient().newCall(request).execute();
        try {
            if (!response.isSuccessful()) {
                throw new BizException(response.body().string());
            } else {
                String body = response.body().string();
                System.out.println(body);
                return body;
            }
        } finally {
            IOUtils.closeQuietly(response.body());
        }
    }

    /**
     * s3 下载文件
     *
     * @param url                 地址
     * @param contentType         编码格式
     * @param cryptoStreamFactory 加密方法
     * @return
     * @throws Exception
     */
    public static String download(String url, String contentType) throws Exception {

        URI uri = new URI(url);
        Map<String, String> headerMap = new HashMap<>();
        Map<String, String> paramsMap = MapUrlParamsUtils.getUrlParams(uri.getQuery());
        //时间戳去url的query参数中的值
//        String timeStamp = paramsMap.get("X-Amz-Date");
//        String dateStamp = paramsMap.get("X-Amz-Date").split("T")[0];
        //加密参数去除Signature
        paramsMap.remove("X-Amz-Signature");
//        String serviceName = "s3";
        headerMap.put("host", uri.getHost());
        headerMap.put("content-type", contentType);
        Request.Builder requestBuilder = createSPHeader(headerMap);
        //把流文件加密再返回byte格式
        Request request = requestBuilder
                .url(url)
                .get()
                .build();
        Response response = getUnsafeOkHttpClient().newCall(request).execute();
        try {
            if (!response.isSuccessful()) {
                throw new BizException(response.body().string());
            } else {
                InputStream inputStream= response.body().byteStream();
                byte[] bytes = null;
                ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
                byte[] buff = new byte[100];
                int rc = 0;
                while ((rc = inputStream.read(buff, 0, 100)) > 0) {
                    swapStream.write(buff, 0, rc);
                }
                bytes = swapStream.toByteArray();
                String s = new String(bytes, "shift-JIS");
                System.out.println(s);
                logger.info(s);
                return s;
            }
        } finally {
            IOUtils.closeQuietly(response.body());
        }
    }

    /**
     * 构建header参数
     *
     * @param headerMap
     * @return
     */
    public static Request.Builder createSPHeader(Map<String, String> headerMap) {
        Request.Builder requestBuilder = new Request.Builder();
        Iterator<Map.Entry<String, String>> pairs =
                headerMap.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, String> data = pairs.next();
            requestBuilder.addHeader(data.getKey(), data.getValue());
        }
        return requestBuilder;
    }


    /**
     * 二进制值转换为十六进制
     *
     * @param bytes
     * @return
     */
    private static String byteToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            if ((bytes[i] & 0xff) < 0x10)
                result.append("0");
            result.append(Long.toString(bytes[i] & 0xff, 16));
        }
        return result.toString().toLowerCase();
    }

    private static String getTimeStamp() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        Calendar c = Calendar.getInstance();
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        return (dateFormat.format(c.getTime()));
    }

    private static String getDateStamp() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        Calendar c = Calendar.getInstance();
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        return (dateFormat.format(c.getTime()));
    }

    /***
     * post请求
     * @param url
     * @param params
     * @return
     * @throws Exception
     */
    public static String doPost(String url, String params) throws Exception {
        OkHttpClient.Builder client = getUnsafeOkHttpClient().newBuilder();
        client.connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);
        Request request = new Request.Builder()
                .addHeader("Content-Type", "text/xml")
                .addHeader("Accept", "Accept: text/plain, */*")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3724.8 Safari/537.36")
                .addHeader("x-amazon-user-agent", "AmazonJavascriptScratchpad/1.0 (Language=Javascript)")
                .addHeader("X-Requested-With", "XMLHttpRequest")
                .addHeader("Content-MD5", computeContentMD5Header(new ByteArrayInputStream(params.getBytes())))
                .post(RequestBody.create(MediaType.get("text/xml"), params))
                .url(url)
                .build();
        Response response = null;
        String body = "";
        try {
            response = client.build().newCall(request).execute();
            body = response.body().string();
            System.out.println(body);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return XmlUtils.xml2Json(body).toJSONString();
    }

    /**
     * 对传递参数转换
     *
     * @param data
     * @param parameters
     * @return
     */
    private static String sortParams(StringBuilder data, Map<String, String> parameters) {
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);

        Iterator<Entry<String, String>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Entry<String, String> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append(pair.getKey() + "=" + pair.getValue());
            } else {
                data.append(pair.getKey() + "=");
            }
            if (pairs.hasNext()) {
                data.append("&");
            }
        }
        return data.toString();
    }

    /**
     * Calculate content MD5 header values for feeds stored on disk.
     */
    public static String computeContentMD5HeaderValue(FileInputStream fis)
            throws IOException, NoSuchAlgorithmException {

        DigestInputStream dis = new DigestInputStream(fis,
                MessageDigest.getInstance("MD5"));

        byte[] buffer = new byte[8192];
        while (dis.read(buffer) > 0) ;

        String md5Content = new String(
                org.apache.commons.codec.binary.Base64.encodeBase64(
                        dis.getMessageDigest().digest()));

        // Effectively resets the stream to be beginning of the file
        // via a FileChannel.
        fis.getChannel().position(0);

        return md5Content;
    }

    /**
     * Consume the stream and return its Base-64 encoded MD5 checksum.
     */
    public static String computeContentMD5Header(InputStream inputStream) {
        // Consume the stream to compute the MD5 as a side effect.
        DigestInputStream s;
        try {
            s = new DigestInputStream(inputStream,
                    MessageDigest.getInstance("MD5"));
            // drain the buffer, as the digest is computed as a side-effect
            byte[] buffer = new byte[8192];
            while (s.read(buffer) > 0) ;
            return new String(
                    org.apache.commons.codec.binary.Base64.encodeBase64(
                            s.getMessageDigest().digest()),
                    "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 作用域
     *
     * @param dateStamp
     * @param regionName
     * @param serviceName
     * @return
     */
    private static String getCredential(String dateStamp, String regionName, String serviceName) {
        String credential = dateStamp + "/" + regionName + "/" + serviceName + "/aws4_request";
        System.out.println("作用域\n" + credential);
        return credential;
    }

    /**
     * 2. 构造签名内容 Algorithm + \n +
     * RequestDateTime + \n +
     * CredentialScope + \n +
     * HashedCanonicalRequest
     *
     * @param timeStamp  x-amz-date标头中以YYYYMMDD'T'HHMMSS'Z'格式指定
     * @param credential 作用域
     * @param request    hash加密过的请求
     * @return
     */
    private static String getSignatureContent(String timeStamp, String credential, String request) {
        String content = "AWS4-HMAC-SHA256" + "\n" + timeStamp + "\n" + credential + "\n" + request;
        System.out.println("签名内容\n" + content);
        return content;
    }

    /**
     * 4.签名
     *
     * @param signatureKey     签名key
     * @param signatureContent 签名内容
     * @return
     * @throws Exception
     */
    static byte[] getSignatureData(byte[] signatureKey, String signatureContent) throws Exception {
        return HmacSHA256(signatureContent, signatureKey);
    }

    static byte[] HmacSHA256(String data, byte[] key) throws Exception {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF-8"));
    }

    /**
     * 3. 构造签名key
     *
     * @param key         secret access key  AWS私有访问密钥
     * @param dateStamp   日期域
     * @param regionName  地区
     * @param serviceName 调用服务名
     * @return
     * @throws Exception
     */
    static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes();
        byte[] kDate = HmacSHA256(dateStamp, kSecret);
        byte[] kRegion = HmacSHA256(regionName, kDate);
        byte[] kService = HmacSHA256(serviceName, kRegion);
        byte[] kSigning = HmacSHA256("aws4_request", kService);
        System.out.println("签名key\n" + byteToHex(kSigning));
        return kSigning;
    }


    /**
     * 1. signV4创建规范请求
     *
     * @param requestMethod
     * @param parameters
     * @param headerParameters
     * @param serviceUrl
     * @param payLoad
     * @return
     * @throws URISyntaxException
     */
    private static String canonicalRequestString(String requestMethod,
                                                 Map<String, Object> parameters, Map<String, String> headerParameters, String serviceUrl, String payLoad)
            throws URISyntaxException {
        URI endpoint = new URI(serviceUrl);
        StringBuilder data = new StringBuilder();
        //HTTPRequestMethod
        data.append(requestMethod + "\n");
        //CanonicalURI
        data.append(endpoint.getPath());
        data.append("\n");
        // CanonicalQueryString
        data.append(sortParams(parameters));
        data.append("\n");
        //CanonicalHeaders
        data.append(sortHeaderParams(headerParameters));
        data.append("\n");
        //SignedHeaders
        data.append(sortSignHeaderParams(headerParameters));
        data.append("\n");
        //HexEncode(Hash(RequestPayload))
        data.append(SHA256Utils.getSHA256(payLoad));
//        System.out.println("payLoad\n" + payLoad);
//        data.append("2d2844666b18a9c550376b9971a2c4b773a0661fd31352fd0342ea3b8c382d5f");
        System.out.println("规范请求\n" + data.toString());
        return data.toString();
    }

    /**
     * 1.1 对传递参数转换(Body)
     *
     * @param parameters
     * @return
     */
    public static String sortParams(Map<String, Object> parameters) {
        StringBuilder data = new StringBuilder();
        if (parameters == null) return "";
        Map<String, Object> sorted = new TreeMap<String, Object>();
        sorted.putAll(parameters);
        Iterator<Map.Entry<String, Object>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, Object> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append(URLEncoder.encode(pair.getKey()) + "=" + URLEncoder.encode(pair.getValue().toString()));
            } else {
                data.append(URLEncoder.encode(pair.getKey()) + "=");
            }
            if (pairs.hasNext()) {
                data.append("&");
            }
        }
        System.out.println("参数转换(Body)\n" + data.toString());
        return data.toString();
    }


    /**
     * 1.2 对传递参数转换(Header)
     *
     * @param parameters
     * @return
     */
    private static String sortHeaderParams(Map<String, String> parameters) {
        StringBuilder data = new StringBuilder();
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);
        Iterator<Map.Entry<String, String>> pairs =
                sorted.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, String> pair = pairs.next();
            if (pair.getValue() != null) {
                data.append(pair.getKey().toLowerCase() + ":" + pair.getValue().trim());
            } else {
                data.append(pair.getKey().toLowerCase() + ":");
            }
            data.append("\n");
        }
        System.out.println("参数转换(Header)\n" + data.toString());
        return data.toString();
    }

    /**
     * 1.3 对传递参数转换(SignHeader)
     *
     * @param parameters
     * @return
     */
    private static String sortSignHeaderParams(Map<String, String> parameters) {
        StringBuilder data = new StringBuilder();
        Map<String, String> sorted = new TreeMap<String, String>();
        sorted.putAll(parameters);
        Iterator<String> pairs =
                sorted.keySet().iterator();
        while (pairs.hasNext()) {
            String key = pairs.next();
            if (key != null) {
                data.append(key.toLowerCase());
            }
            if (pairs.hasNext()) {
                data.append(";");
            }
        }
        System.out.println("参数转换(SignHeader)\n" + data.toString());
        return data.toString();
    }

}
